// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//
// You may obtain a copy of the License at
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.perf.network;

import androidx.annotation.Keep;
import com.google.firebase.perf.metrics.NetworkRequestMetricBuilder;
import com.google.firebase.perf.transport.TransportManager;
import com.google.firebase.perf.util.Timer;
import java.io.IOException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.protocol.HttpContext;

/**
 * These are the functions that are bytecode instrumented into the apk and methods that collect the
 * information for the NetworkRequestMetric for Apache Http functions.
 */
@SuppressWarnings("deprecation") // Apache http library is needed for instrumentation
public class FirebasePerfHttpClient {

  private FirebasePerfHttpClient() {}

  /**
   * Instrumented function for Apache HttpClient.execute(request)
   *
   * @return HttpResponse from executing request
   * @throws IOException if unable to execute request or receive response
   */
  @Keep
  public static HttpResponse execute(final HttpClient client, final HttpUriRequest request)
      throws IOException {
    return execute(client, request, new Timer(), TransportManager.getInstance());
  }

  /**
   * Instrumented function for Apache HttpClient.execute(request, context)
   *
   * @return HttpResponse from executing request
   * @throws IOException if unable to execute request or receive response
   */
  @Keep
  public static HttpResponse execute(
      final HttpClient client, final HttpUriRequest request, final HttpContext context)
      throws IOException {
    return execute(client, request, context, new Timer(), TransportManager.getInstance());
  }

  /**
   * Instrumented function for Apache HttpClient.execute(request, responseHandler)
   *
   * @return the response object as generated by the response handler
   * @throws IOException if unable to execute request or receive response
   */
  @Keep
  public static <T> T execute(
      final HttpClient client,
      final HttpUriRequest request,
      final ResponseHandler<T> responseHandler)
      throws IOException {
    return execute(client, request, responseHandler, new Timer(), TransportManager.getInstance());
  }

  /**
   * Instrumented function for Apache HttpClient.execute(request, responseHandler, context)
   *
   * @return the response object as generated by the response handler
   * @throws IOException if unable to execute request or receive response
   */
  @Keep
  public static <T> T execute(
      final HttpClient client,
      final HttpUriRequest request,
      final ResponseHandler<T> responseHandler,
      final HttpContext context)
      throws IOException {
    return execute(
        client, request, responseHandler, context, new Timer(), TransportManager.getInstance());
  }

  /**
   * Instrumented function for Apache HttpClient.execute(target, request)
   *
   * @return HttpResponse from executing request
   * @throws IOException if unable to execute request or receive response
   */
  @Keep
  public static HttpResponse execute(
      final HttpClient client, final HttpHost target, final HttpRequest request)
      throws IOException {
    return execute(client, target, request, new Timer(), TransportManager.getInstance());
  }

  /**
   * Instrumented function for Apache HttpClient.execute(target, request, context)
   *
   * @return HttpResponse from executing request
   * @throws IOException if unable to execute request or receive response
   */
  @Keep
  public static HttpResponse execute(
      final HttpClient client,
      final HttpHost target,
      final HttpRequest request,
      final HttpContext context)
      throws IOException {
    return execute(client, target, request, context, new Timer(), TransportManager.getInstance());
  }

  /**
   * Instrumented function for Apache HttpClient.execute(target, request, responseHandler)
   *
   * @return the response object as generated by the response handler
   * @throws IOException if unable to execute request or receive response
   */
  @Keep
  public static <T> T execute(
      final HttpClient client,
      final HttpHost target,
      final HttpRequest request,
      final ResponseHandler<? extends T> responseHandler)
      throws IOException {
    return execute(
        client, target, request, responseHandler, new Timer(), TransportManager.getInstance());
  }

  /**
   * Instrumented function for Apache HttpClient.execute(target, request, responseHandler, context)
   *
   * @return the response object as generated by the response handler
   * @throws IOException if unable to execute request or receive response
   */
  @Keep
  public static <T> T execute(
      final HttpClient client,
      final HttpHost target,
      final HttpRequest request,
      final ResponseHandler<? extends T> responseHandler,
      final HttpContext context)
      throws IOException {
    return execute(
        client,
        target,
        request,
        responseHandler,
        context,
        new Timer(),
        TransportManager.getInstance());
  }

  /**
   * Send network metric for Apache HttpClient.execute(HttpUriRequest request)
   *
   * @return HttpResponse from executing request
   * @throws IOException if unable to execute request or receive response
   */
  static HttpResponse execute(
      final HttpClient client,
      final HttpUriRequest request,
      final Timer timer,
      final TransportManager transportManager)
      throws IOException {
    HttpResponse response = null;
    NetworkRequestMetricBuilder builder = NetworkRequestMetricBuilder.builder(transportManager);
    try {
      builder.setUrl(request.getURI().toString()).setHttpMethod(request.getMethod());
      Long requestContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(request);
      if (requestContentLength != null) {
        builder.setRequestPayloadBytes(requestContentLength);
      }

      // Execute network Request
      timer.reset();
      builder.setRequestStartTimeMicros(timer.getMicros());
      response = client.execute(request);
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      builder.setHttpResponseCode(response.getStatusLine().getStatusCode());
      Long responseContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(response);
      if (responseContentLength != null) {
        builder.setResponsePayloadBytes(responseContentLength);
      }
      String contentType =
          NetworkRequestMetricBuilderUtil.getApacheHttpResponseContentType(response);
      if (contentType != null) {
        builder.setResponseContentType(contentType);
      }
      builder.build();
    } catch (IOException e) {
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      NetworkRequestMetricBuilderUtil.logError(builder);
      throw e;
    }
    return response;
  }

  /**
   * Send network metric for Apache HttpClient.execute(HttpUriRequest request, HttpContext context)
   *
   * @return HttpResponse from executing request
   * @throws IOException if unable to execute request or receive response
   */
  static HttpResponse execute(
      final HttpClient client,
      final HttpUriRequest request,
      final HttpContext context,
      final Timer timer,
      final TransportManager transportManager)
      throws IOException {
    HttpResponse response = null;
    NetworkRequestMetricBuilder builder = NetworkRequestMetricBuilder.builder(transportManager);
    try {
      builder.setUrl(request.getURI().toString()).setHttpMethod(request.getMethod());
      Long requestContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(request);
      if (requestContentLength != null) {
        builder.setRequestPayloadBytes(requestContentLength);
      }

      // Execute network Request
      timer.reset();
      builder.setRequestStartTimeMicros(timer.getMicros());
      response = client.execute(request, context);
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      builder.setHttpResponseCode(response.getStatusLine().getStatusCode());
      Long responseContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(response);
      if (responseContentLength != null) {
        builder.setResponsePayloadBytes(responseContentLength);
      }
      String contentType =
          NetworkRequestMetricBuilderUtil.getApacheHttpResponseContentType(response);
      if (contentType != null) {
        builder.setResponseContentType(contentType);
      }
      builder.build();
    } catch (IOException e) {
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      NetworkRequestMetricBuilderUtil.logError(builder);
      throw e;
    }
    return response;
  }

  /**
   * Send network metric for Apache HttpClient.execute(HttpUriRequest request, ResponseHandler<?
   * extends T> responseHandler)
   *
   * @return <T> T
   * @throws IOException if unable to execute request or receive response
   */
  static <T> T execute(
      final HttpClient client,
      final HttpUriRequest request,
      final ResponseHandler<T> responseHandler,
      final Timer timer,
      final TransportManager transportManager)
      throws IOException {
    NetworkRequestMetricBuilder builder = NetworkRequestMetricBuilder.builder(transportManager);
    try {
      builder.setUrl(request.getURI().toString()).setHttpMethod(request.getMethod());
      Long requestContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(request);
      if (requestContentLength != null) {
        builder.setRequestPayloadBytes(requestContentLength);
      }

      // Execute network Request
      timer.reset();
      builder.setRequestStartTimeMicros(timer.getMicros());
      return client.execute(
          request, new InstrumentApacheHttpResponseHandler<T>(responseHandler, timer, builder));

    } catch (IOException e) {
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      NetworkRequestMetricBuilderUtil.logError(builder);
      throw e;
    }
  }

  /**
   * Send network metric for Apache HttpClient.execute(HttpUriRequest request, ResponseHandler<?
   * extends T> responseHandler, HttpContext context)
   *
   * @return <T> T
   * @throws IOException if unable to execute request or receive response
   */
  static <T> T execute(
      final HttpClient client,
      final HttpUriRequest request,
      final ResponseHandler<T> responseHandler,
      final HttpContext context,
      final Timer timer,
      final TransportManager transportManager)
      throws IOException {
    NetworkRequestMetricBuilder builder = NetworkRequestMetricBuilder.builder(transportManager);
    try {
      builder.setUrl(request.getURI().toString()).setHttpMethod(request.getMethod());
      Long requestContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(request);
      if (requestContentLength != null) {
        builder.setRequestPayloadBytes(requestContentLength);
      }

      // Execute network Request
      timer.reset();
      builder.setRequestStartTimeMicros(timer.getMicros());
      return client.execute(
          request,
          new InstrumentApacheHttpResponseHandler<T>(responseHandler, timer, builder),
          context);

    } catch (IOException e) {
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      NetworkRequestMetricBuilderUtil.logError(builder);
      throw e;
    }
  }

  /**
   * Send network metric for Apache HttpClient.execute(HttpHost target, HttpRequest request)
   *
   * @return HttpResponse from executing request
   * @throws IOException if unable to execute request or receive response
   */
  static HttpResponse execute(
      final HttpClient client,
      final HttpHost target,
      final HttpRequest request,
      final Timer timer,
      final TransportManager transportManager)
      throws IOException {
    HttpResponse response = null;
    NetworkRequestMetricBuilder builder = NetworkRequestMetricBuilder.builder(transportManager);
    try {
      builder
          .setUrl(target.toURI() + request.getRequestLine().getUri())
          .setHttpMethod(request.getRequestLine().getMethod());
      Long requestContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(request);
      if (requestContentLength != null) {
        builder.setRequestPayloadBytes(requestContentLength);
      }

      // Execute network Request
      timer.reset();
      builder.setRequestStartTimeMicros(timer.getMicros());
      response = client.execute(target, request);
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      builder.setHttpResponseCode(response.getStatusLine().getStatusCode());
      Long responseContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(response);
      if (responseContentLength != null) {
        builder.setResponsePayloadBytes(responseContentLength);
      }
      String contentType =
          NetworkRequestMetricBuilderUtil.getApacheHttpResponseContentType(response);
      if (contentType != null) {
        builder.setResponseContentType(contentType);
      }
      builder.build();
    } catch (IOException e) {
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      NetworkRequestMetricBuilderUtil.logError(builder);
      throw e;
    }
    return response;
  }

  /**
   * Send network metric for Apache HttpClient.execute(HttpHost target, HttpRequest request,
   * HttpContext context)
   *
   * @return HttpResponse from executing request
   * @throws IOException if unable to execute request or receive response
   */
  static HttpResponse execute(
      final HttpClient client,
      final HttpHost target,
      final HttpRequest request,
      final HttpContext context,
      final Timer timer,
      final TransportManager transportManager)
      throws IOException {
    HttpResponse response = null;
    NetworkRequestMetricBuilder builder = NetworkRequestMetricBuilder.builder(transportManager);
    try {
      builder
          .setUrl(target.toURI() + request.getRequestLine().getUri())
          .setHttpMethod(request.getRequestLine().getMethod());
      Long requestContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(request);
      if (requestContentLength != null) {
        builder.setRequestPayloadBytes(requestContentLength);
      }

      // Execute network Request
      timer.reset();
      builder.setRequestStartTimeMicros(timer.getMicros());
      response = client.execute(target, request, context);
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      builder.setHttpResponseCode(response.getStatusLine().getStatusCode());
      Long responseContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(response);
      if (responseContentLength != null) {
        builder.setResponsePayloadBytes(responseContentLength);
      }
      String contentType =
          NetworkRequestMetricBuilderUtil.getApacheHttpResponseContentType(response);
      if (contentType != null) {
        builder.setResponseContentType(contentType);
      }
      builder.build();
    } catch (IOException e) {
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      NetworkRequestMetricBuilderUtil.logError(builder);
      throw e;
    }
    return response;
  }

  /**
   * Send network metric for Apache HttpClient.execute(HttpHost target, HttpRequest request,
   * ResponseHandler<? extends T> responseHandler)
   *
   * @return <T> T
   * @throws IOException if unable to execute request or receive response
   */
  static <T> T execute(
      final HttpClient client,
      final HttpHost target,
      final HttpRequest request,
      final ResponseHandler<? extends T> responseHandler,
      final Timer timer,
      final TransportManager transportManager)
      throws IOException {
    NetworkRequestMetricBuilder builder = NetworkRequestMetricBuilder.builder(transportManager);
    try {
      builder
          .setUrl(target.toURI() + request.getRequestLine().getUri())
          .setHttpMethod(request.getRequestLine().getMethod());
      Long requestContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(request);
      if (requestContentLength != null) {
        builder.setRequestPayloadBytes(requestContentLength);
      }

      // Execute network Request
      timer.reset();
      builder.setRequestStartTimeMicros(timer.getMicros());
      return client.execute(
          target,
          request,
          new InstrumentApacheHttpResponseHandler<T>(responseHandler, timer, builder));

    } catch (IOException e) {
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      NetworkRequestMetricBuilderUtil.logError(builder);
      throw e;
    }
  }

  /**
   * Send network metric for Apache HttpClient.execute(HttpHost target, HttpRequest request,
   * ResponseHandler<? extends T> responseHandler, HttpContext context)
   *
   * @return <T> T
   * @throws IOException if unable to execute request or receive response
   */
  static <T> T execute(
      final HttpClient client,
      final HttpHost target,
      final HttpRequest request,
      final ResponseHandler<? extends T> responseHandler,
      final HttpContext context,
      final Timer timer,
      final TransportManager transportManager)
      throws IOException {
    NetworkRequestMetricBuilder builder = NetworkRequestMetricBuilder.builder(transportManager);
    try {
      builder
          .setUrl(target.toURI() + request.getRequestLine().getUri())
          .setHttpMethod(request.getRequestLine().getMethod());
      Long requestContentLength =
          NetworkRequestMetricBuilderUtil.getApacheHttpMessageContentLength(request);
      if (requestContentLength != null) {
        builder.setRequestPayloadBytes(requestContentLength);
      }

      // Execute network Request
      timer.reset();
      builder.setRequestStartTimeMicros(timer.getMicros());
      return client.execute(
          target,
          request,
          new InstrumentApacheHttpResponseHandler<T>(responseHandler, timer, builder),
          context);

    } catch (IOException e) {
      builder.setTimeToResponseCompletedMicros(timer.getDurationMicros());
      NetworkRequestMetricBuilderUtil.logError(builder);
      throw e;
    }
  }
}
